/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package dale2507.gates.gate;

import dale2507.gates.GatePlugin;
import dale2507.gates.Messenger;
import dale2507.gates.data.Settings;
import dale2507.gates.events.GateStatusChangeEvent;
import dale2507.gates.events.GateTravelEvent;
import dale2507.gates.exceptions.TravelException;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockFromToEvent;
import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.event.vehicle.VehicleMoveEvent;
import org.bukkit.plugin.PluginManager;
import org.bukkit.util.Vector;

/**
 *
 * @author Dale
 */
public abstract class IRing implements Listener {
    
    //<editor-fold defaultstate="collapsed" desc="Variables">
    private Gate gate;
    private Material chevron;
    private IIris iris;
    //</editor-fold>
    
    //<editor-fold defaultstate="collapsed" desc="Constructors">
    IRing() {
    }
    
    IRing(DataInputStream dis) throws Exception {
    }
    //</editor-fold>
    
    //<editor-fold defaultstate="collapsed" desc="Initialiser">
    void init() {
        // remove any chevrons
        this.setAllChevrons(false);
        
        // remove event horizon
        this.enableEventHorizon(false);
        
        PluginManager pm = Bukkit.getServer().getPluginManager();
        pm.registerEvents(this, GatePlugin.getInstance());
    }
    //</editor-fold>
    
    //<editor-fold defaultstate="collapsed" desc="Gate setter/getter">
    void setGate(Gate gate) {
        this.gate = gate;
        this.iris = new KeyIris(this.gate, this.getIrisPowerBlock());
    }
    
    public Gate getGate() {
        return this.gate;
    }

    protected IIris getIris() {
        return this.iris;
    }
    //</editor-fold>
    
    //<editor-fold defaultstate="collapsed" desc="Abstract methods">
    abstract Block[] getRingBlocks();
    abstract Block[] getInnerBlocks();
    public abstract Location getExit();
    protected abstract void setChevron(int chevron, boolean on);
    abstract Block getIrisPowerBlock();
    //</editor-fold>
    
    //<editor-fold defaultstate="collapsed" desc="Chevron methods">
    private void setAllChevrons(boolean on) {
        for (int x=0; x<9; x++) {
            this.setChevron(x, on);
        }
    }
    
    /**
     * Sets the material to be used for the chevrons. Taken from gate settings torch type
     * @param m Material to be used for chevrons
     */
    void setChevronMaterial(Material m) {
        chevron = m;
    }
    
    /**
     * Gets the material assign to the chevrons
     * @return Material being used for the chevrons
     */
    protected Material getChevronMaterial() {
        return chevron;
    }
    //</editor-fold>
    
    //<editor-fold defaultstate="collapsed" desc="Event horizon and isClear">
    final boolean isClear() {
        int count = 0;
        for (Block b : getInnerBlocks()) {
            if (!b.isEmpty() && !b.isLiquid()) count++;
        }
        if (!this.getExit().getBlock().isEmpty() && !this.getExit().getBlock().isLiquid()) count++;
        if (count == getInnerBlocks().length) return false;
        return true;
    }
    
    private void enableEventHorizon(boolean on) {
        for (Block b : getInnerBlocks()) {
            if (on && this.isClear()) {
                b.setType(Material.STATIONARY_WATER);
            } else if (b.isLiquid()) {
                b.setType(Material.AIR);
            }
        }
    }
    //</editor-fold>

    /**
     * Saves the ring to an output stream.
     * @param dos Output stream to save the ring to
     * @throws Exception Thrown if an error occurs writing to the stream
     */
    public abstract void save(DataOutputStream dos) throws Exception;
    
    //<editor-fold defaultstate="collapsed" desc="General Listeners">
    /**
     * Stops the event horizon from moving out of the ring
     * @param event The event that was triggered
     */
    @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
    public void onEventHorizonFromTo(BlockFromToEvent event) {
        if (this.getGate().getStatus() == Gate.Status.INCOMING || this.getGate().getStatus() == Gate.Status.OUTGOING) {
            if (event.getBlock().getType().equals(Material.WATER) || event.getBlock().getType().equals(Material.STATIONARY_WATER)) {
                Block[] inner = this.getInnerBlocks();
                for (Block b : inner) {
                    if (event.getBlock().equals(b)) {
                        event.setCancelled(true);
                        break;
                    }
                }
            }
        }
    }
    
    /**
     * Stops the ring from being broken before the display (Sign)
     * @param event The event that was triggered
     */
    @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
    public void onRingBroken(BlockBreakEvent event) {
        if (event.getBlock().getType().equals(Settings.getInstance().creation.getGateMaterial())) {
            Block[] ring = this.getRingBlocks();
            for (Block b : ring) {
                if (event.getBlock().equals(b)) {
                    event.setCancelled(true);
                    Messenger.sendMessage(event.getPlayer(), Messenger.ERROR, "You must destroy the sign before you can destroy a gate");
                    break;
                }
            }
        }
    }
    
    /**
     * Stops the ring from being broken before the display (Sign)
     * @param event The event that was triggered
     */
    @EventHandler(priority = EventPriority.NORMAL)
    public void onGateStatusChange(GateStatusChangeEvent event) {
        if (event.getGate() == this.gate) {
            if (event.getNewStatus() == Gate.Status.INCOMING || event.getNewStatus() == Gate.Status.OUTGOING) {
                this.enableEventHorizon(true);
                this.setAllChevrons(true);
            } else {
                this.enableEventHorizon(false);
                this.setAllChevrons(false);
            }
        }
    }
    //</editor-fold>
    
    //<editor-fold defaultstate="collapsed" desc="Movement Listeners">
    /**
     * Stops the ring from being broken before the display (Sign)
     * @param event The event that was triggered
     */
    @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
    public void onPlayerEnter(PlayerMoveEvent event) {
        if (this.getGate().getStatus() == Gate.Status.OUTGOING || this.getGate().getStatus() == Gate.Status.INCOMING) {
            if (event.getPlayer().getHealth() > 0) {
                Block to = event.getTo().getBlock();
                for (Block b : this.getInnerBlocks()) {
                    if (b.equals(to)) {
                        try {
                            Location l = this.transport(event.getPlayer());
                            event.setFrom(l);
                            event.setTo(l);
                        } catch (TravelException ex) {
                            if (Settings.getInstance().connection.killOnWrongWay()) event.getPlayer().setHealth(0);
                            Messenger.sendMessage(event.getPlayer(), Messenger.ERROR, ex.getMessage());
                        }
                    }
                }
            }
        }
    }
    
    /**
     * Transports a vehicle which enters the event horizon
     * @param event The event that was triggered
     */
    @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
    public void onVehicleEnter(VehicleMoveEvent event) {
        // Check if settings allow
        if (Settings.getInstance().connection.allowVehicles()) {
            // Check that gate is active
            if (this.getGate().getStatus() == Gate.Status.OUTGOING || this.getGate().getStatus() == Gate.Status.INCOMING) {
                // Check that vehicle is entering gate
                Block to = event.getTo().getBlock();
                for (Block b : this.getInnerBlocks()) {
                    if (b.equals(to)) {
                        // Get player if is passenger
                        Player p = null;
                        if (event.getVehicle().getPassenger() != null &&
                                event.getVehicle().getPassenger() instanceof Player)
                            p = (Player) event.getVehicle().getPassenger();

                        try {
                            this.transport(event.getVehicle());
                        } catch (TravelException ex) {
                            event.getVehicle().remove();
                            if (p != null) {
                                if (Settings.getInstance().connection.killOnWrongWay()) p.setHealth(0);
                                Messenger.sendMessage(p, Messenger.ERROR, ex.getMessage());
                            }
                        }
                    }
                }
            }
        }
    }
    //</editor-fold>
    
    //<editor-fold defaultstate="collapsed" desc="Private transport methods">
    private Location transport(Entity entity) throws TravelException {
        if (this.getGate().getStatus() == Gate.Status.OUTGOING) {
            GateTravelEvent e = new GateTravelEvent(this.gate, this.getGate().getActivator().getDestination(), entity);
            Bukkit.getServer().getPluginManager().callEvent(e);
            
            IRing destRing = this.getGate().getActivator().getDestination().getRing();
            if (destRing.isClear()) {
                Location exit = destRing.getExit();
                
                // Preserve velocity
                this.preserveVelocity(entity, this.getExit(), exit);
                
                entity.teleport(exit);
                return exit;
            } else {
                throw new TravelException("Could not rematerialise you at the destination");
            }
        } else {
            throw new TravelException("You cannot enter a gate conenction backwards");
        }
    }
    
    private void preserveVelocity(Entity entity, Location start, Location end) {
        Vector v = entity.getVelocity();
        double velocity;
        
        // get velocity into start
        if (start.getPitch() != 0) {
            velocity = Math.abs(v.getY());
        } else {
            if (start.getYaw() == 0 || start.getYaw() == 180) {
                velocity = Math.abs(v.getZ());
            } else {
                velocity = Math.abs(v.getX());
            }
        }
        
        // set velocity out of end
        v = new Vector();
        if (end.getPitch() == 90) {
            v.setY(0 - velocity);
        } else if (end.getPitch() == -90) {
            v.setY(velocity);
        } else {
            switch ((int) end.getYaw()) {
                case 0:
                    v.setZ(velocity);
                    break;
                case 180:
                    v.setZ(0 - velocity);
                    break;
                case 90:
                    v.setX(0 - velocity);
                    break;
                case 270:
                    v.setX(velocity);
                    break;
            }
        }
        entity.setVelocity(v);
    }
    //</editor-fold>

    void removeListeners() {
        HandlerList.unregisterAll(this);
    }
    
}
